Глава 18

СОБЫТИЯ

Как уже неоднократно подчеркивалось, программы, работающие в среде Turbo Vision,- это программы, управляемые событиями. В этой главе подробно рассматривается механизм событий и способы их использования.

18.1. ПРИРОДА СОБЫТИЙ

События представляют собой небольшие пакеты информации, которыми обмениваются видимые элементы и которые создаются средой Turbo Vision в ответ на те или иные действия пользователя. Нажатие на любую клавишу или манипуляция мышью порождает событие, которое передается по цепочке активности видимых элементов до тех пор, пока не найдется элемент, знающий как обработать это событие. Может оказаться, что в программе нет ни одного элемента, способного обработать событие. В этом случае обычно ничего не происходит, по умолчанию Turbo Vision просто удаляет ненужное событие, однако программист может предусмотреть свою реакцию в этой ситуации.

Важно помнить, что события сами по себе не производят никаких действий в программе, но в ответ на событие могут создаваться новые видимые элементы, модифицироваться или уничтожаться существующие элементы, что и приводит к изменению содержимого экрана. Иными словами, все действия по созданию и изменению изображения реализуются видимыми объектами, а события лишь управляют их работой -именно это мы имеем в виду, говоря о программах, управляемых событиями.

Технически событие представляет собой обычную для Турбо Паскаля запись, имеющую следующую структуру:

type

TEvent = record 

What: Word;{Тип события} 

case Word of evNothing: (); {Пустое событие} 

evMouse: ( {Событие от мыши:}

Buttons: Byte; {Состояние кнопок} 

Double : Boolean; {Признак двойного нажатия} 

Where : TPoint); {Координаты мыши} 

evKeyDown: ( {Событие от клавиатуры:}

case Integer of

0: (KeyCode: Word); {Код клавиши} 

1: (CharCode: Char; 

ScanCode: Byte)); 

evMessage: ( {Событие-сообщение} 

Command: Word; {Код команды} 

case Word of

0: (InfoPtr : Pointer) 

1: (InfoLong: Longlnt)

2: (InfoWord: Word);

3: (Infolnt : Integer);

4: (InfoByte: Byte);

5: (InfoChar: Char));

end;

Ядром события является поле What, которое описывает тип события. Оставшаяся часть записи содержит дополнительную информацию, например, код нажатой клавиши или координаты точки, в которой была нажата кнопка мыши.

18.2. ВИДЫ СОБЫТИЙ

Существуют 4 основных класса событий: события от мыши, события от клавиатуры, сообщения и пустые события. Внутри класса события могут подразделяться на виды. Например, класс событий от мыши состоит из таких видов, как перемещение указателя мыши, нажатие на кнопку мыши и т.п. Каждый класс имеет определенную маску, с помощью которой можно легко идентифицировать класс события, не анализируя его вид. Например, вместо того, чтобы проверять 4 различных вида событий от мыши, можно просто проверить, пришло ли это событие от мыши или оно имеет другой источник. Два следующих оператора идентичны:

if Event.What and ((svMouseDown or 

evMouseUp or evMouseMove or evMouseAuto} <> 0) then ... 

if Event.What and (evMouse <> 0) then ...

Для анализа класса событий используются следующие маски:

evNothing {'Пустое' событие};

evMouse {Событие от мыши};

evKeyboard {Событие от клавиатуры};

evMessage {Сообщение}

На рис. 18.1 показаны разряды поля What и соответствующие маски.

18.2.1. События от мыши

Существуют 4 вида событий от мыши: событие evMouseDown возникает как отклик на нажатие кнопки мыши; когда кнопка отпускается, возникает событие evMouseUp; перемещение указателя мыши порождает событие evMouseMove; наконец, если кнопка мыши удерживается в нажатом состоянии, Turbo Vision периодически генерирует событие evMouseAuto. С каждым событием от мыши в записи TEvent передаются также координаты, которые имеет указатель мыши в данный момент. Отметим, что в отличие о.т других координат Turbo Vision координаты мыши задаются относительно границ экрана, а не относительно границ какого-либо видимого элемента. Горизонтальная координата мыши меняется в диапазоне от 0 до 79, вертикальная - от 0 до 24 (режим 25 строк на экране) или от 0 до 42/49 (режим 43/50 строк).

Рис. 18.1. Разряды поля What

18.2.2. События от клавиатуры

В этом классе событий имеется единственное событие evKeyDown, связанное с нажатием на клавишу. В поле TEvent.KeyCode в этом случае содержится так называемый расширенный код клавиши, который Вы можете интерпретировать как совокупность двух байт: CharCode и ScanCode. При нажатии на алфавитно-цифровую клавишу поле CharCode содержит соответствующий ASCII-символ, при нажатии на функциональную клавишу поле CharCode содержит символ #0, а поле ScanCode - код сканирования нажатой клавиши. Для облегчения идентификации нажатой клавиши можно использовать константы kbXXXX, определенные в интерфейсной части модуля Drivers.

18.2.3. Сообщения

Сообщения бывают трех видов: команды, общие сообщения и пользовательские сообщения. Команды помечаются в поле What флагом evCommand, общие сообщения -флагом evBroadcast и пользовательские сообщения - константой, определенной пользователем. Большинство событий преобразуется в команды. Например, если пользователь отметит мышью какое-то поле строки статуса, сообщение от мыши поступит в конечном счете в обработчик событий строки статуса (любой видимый элемент имеет метод HandleEvent, называемый обработчиком событий, см. п.16.6.2), который определит, какое именно поле было отмечено. С каждым полем строки статуса обычно связана какая-то команда, поэтому обработчик очистит пришедшее к нему сообщение от мыши и создаст новое сообщение, содержащее выбранную команду. Общие и пользовательские сообщения не являются исключением и обычно также преобразуются в команды.

18.2.4. Пустые события

Пустым событие становится после его обработки каким-либо видимым элементом. Технически пустое событие соответствует значению TEvent. What = evNothing = 0. Когда событие обработано, видимый элемент вызывает метод ClearEvent, с помощью которого в поле What устанавливается значение evNothing. Объекты должны игнорировать событие evNothing, поскольку оно уже обработано каким-то видимым элементом.

18.3. МАРШРУТИЗАЦИЯ СОБЫТИЙ

Как уже говорилось, любая программа, работающая в среде Turbo Vision, является прямым или косвенным (через TApplicatiori) потомком TProgram и основана на обращении к трем главным методам этого объекта: Init, Run и Done. Например:

Uses App,...; 

type

TMyProgram = object (TApplication)

.....

end;

..... 

var

MyProgram = TMyProgram;

.....

begin {Исполняемая часть программы:}

MyProgram.Init; {Инициировать программу} 

MyProgram.Run; {Выполнить программу} 

MyProgram.Done {Завершить работу} 

end.

Процесс получения и обработки событий инициируется методом TProgram.Run, который для этого обращается к методу TGroup.Execute (любая .программа является потомком TGroup). Метод TGroup.Execute реализует следующий цикл: 

var

Event: TEvent;

begin

Event.What := evNothing; {Инициировать пустое событие} 

repeat {Основной цикл программы} 

if Event.What <> evNothing then

EventError(Event); {Событие не очищено - ошибка} 

GetEvent(Event); {Получить событие} 

HandleEvent(Event); {Передать событие обработчику} 

until EndState <> Continue;

.....

end;

Метод GetEvent наследуется всеми видимыми элементами от TView и является основным источником событий. Этот метод вначале проверяет, не подготовил ли событие метод PutEvent и, если это так, возвращает его. Затем GetEvent проверяет клавиатуру и мышь. Если какое-то из этих устройств изменило свое состояние, метод формирует и возвращает соответствующее событие. Если изменения не произошло, GetEvent обращается к методу TProgram.Idle, чтобы запустить «фоновую» задачу (подробнее об этом см. п. 18.6).

С помощью вызова метода TGroup.Execute события всегда начинают свой путь с модального видимого элемента. Напомню, что модальный элемент определяет точку диалога; в программе в каждый момент времени есть один и только один модальный элемент (см. п. 17.4). В самом начале программы таким элементом является обычно экземпляр объекта TProgram или его потомка. Если в программе открыто модальное диалоговое окно, оно обратится к TGroup.Execute и, следовательно, путь событий начнется с этого окна. В любом случае начинает обработку события метод HandleEvent модального видимого элемента. Дальнейший маршрут события зависит от того, является ли событие позиционированным, активным или общим.

18.3.1. Позиционированные события

Позиционированные события - это всегда события от мыши (evMouse). Модальный видимый элемент получает позиционированное событие первым и начинает просматривать свои подэлементы в порядке, обратном их Z-упорядочению (см. п. 17.3.2), до тех пор, пока не найдет подэлемент, координаты которого включают в себя координаты указателя мыши. Затем модальный элемент передает событие найденному подэле-менту. Поскольку видимые элементы часто перекрываются на экране, может оказаться, что координата указателя мыши принадлежит более чем одному видимому элементу. Следование обратному Z-упорядочению гарантирует, что событие получит самый верхний видимый элемент. Заметим, что Вам не нужно прослеживать Z-упорядочение элементов в Вашем обработчике событий: достаточно вызвать унаследованный метод HandleEvent, который автоматически направит событие нужным образом.

Процесс передачи события продолжается до тех пор, пока не обнаружится терминальный видимый элемент (например, полоса скроллинга) или не будет найден ни один подэлемент с нужными координатами элемент не знает, как обработать событие, он передает его вверх по активной цепочке своему владельцу. Технически это реализуется с помощью выхода из обработчика HandleEvent вызовом Exit; событие не следует очищать обращением к ClearEvent. Процесс повторяется до тех пор, пока событие не будет обработано или не достигнет модального элемента. Если модальный элемент не знает, как обработать вернувшееся к нему событие, он вызывает метод EventError.

В Turbo Vision существуют средства, позволяющие видимым элементам, не принадлежащим цепочке активности, получать и обрабатывать активные события (см. п. 18.4).

18.3.2. Общие события

Общие события - это общие сообщения или пользовательские сообщения. Общие события не имеют конкретного адресата и посылаются всем подэлементам текущего модального элемента.

Модальный элемент получает событие и начинает передавать его своим подэлементам в Z-порядке. Если какой-то из подэлементов - группа, он также передает событие своим подэлементам и также в Z-порядке. Процесс продолжается до тех пор, пока не будет найден элемент, который обработает событие, или пока событие не получат все видимые элементы, принадлежащие (прямо или косвенно) модальному элементу.

Общие события обычно используются для организации межэлементных связей. Подробнее об этом см. п. 18.7.

18.4. ФАЗА СОБЫТИЙ

Обычно активные события (evKeyDown и evCommand) получают и обрабатывают видимые элементы, принадлежащие цепочке активности. Однако часто возникают ситуации, когда необходимо, чтобы активное событие обработал неактивный элемент. Например, если на экране активно окно скроллера с полосами скроллинга, то события от клавиатуры будут передаваться окну. Как заставить в этом случае полосы реагировать на нажатие клавиш PgUp или PgDn? Для этого в Turbo Vision предусмотрен специальный механизм, основанный на так называемой фазе события. Когда модальный элемент получает событие, его передача выполняется в следующей последовательности:

Таким образом, Вы должны установить флаги ofPreProcess или ofPostProcess (или оба вместе) при инициации видимого элемента, если хотите, чтобы он мог получить активное событие до или после (или и до и после) того, как его получат активные элементы.

Для предыдущего примера необходимо инициировать полосы скроллинга с установленными флагами ofPostProcess, если требуется, чтобы полосы «увидели» и обработали нажатие на клавиши смещения курсора, PgUp, PgDn и т.д. Разумеется, в этом случае полосы получат событие evKeyDown только при условии, что скроллер сам не обработает это событие.

В некоторых ситуациях элемент, перехватывающий событие и до, и после активных элементов, должен модифицировать свое поведение в зависимости от фазы события. Рассмотрим такой типичный пример. Пусть в программе создано диалоговое окно, имеющее строку ввода и три кнопки, для которых определены командные клавиши Q, W, Е. Как добиться того, чтобы эти клавиши использовались в качестве командных клавиш, т.е. приводили к «нажатию» соответствующих кнопок при условии, что активна любая кнопка, а в сочетании с клавишей Аlt - если активна строка ввода (именно так используются командные клавиши в диалоговом окне среды Турбо Паскаля)? Если инициировать кнопки с флагом ofPreProcess, они смогут без труда определить факт нажатия на командную клавишу, однако в строке ввода пользователь не сможет ввести буквы Q, W и E, так как они будут перехвачены кнопками до того, как событие от клавиши получит строка ввода. Если инициировать кнопки с флагом ofPostProcess, пользователь не сможет использовать сочетания Аlt-<клавиша> для нажатия кнопки, если активна строка ввода: все события evKeyDown будут в этом случае направляться в строку. Решение очевидно: нужно определить оба флага, но на препроцессорной фазе следует проверять ввод Аlt-<клавиша>, а на постпроцессорной - <клавиша>.

Для реализации этих проверок обработчик событий объекта TButton должен каким-то образом определить текущую фазу события. С этой целью в любой группе предусмотрено поле Phase. Это поле доступно только для чтения и содержит одно из значений phPreProcess, phFocused или phPostProcess в зависимости от фазы события.

Следующий фрагмент иллюстрирует ту часть обработчика событий кнопок, которая проверяет командную клавишу:

evKeyDown: {Это часть оператора саsе} 

begin

С := HotKey(Title*); {Получаем в С букву клавиши}

{Проверяем Alt-<клавиша>:} 

if (Event.KeyCode = GetAltCode(С)) or 

{Проверяем <клавиша>:}

(Owner*.Phase = phPostProcess) and (C <> #0) 

and (UpCase(Event.CharCode) = C) or 

{Проверяем активность и нажатие пробела:} 

(State and sfFocused <> 0) and (Event.CharCode = ' ') then 

Press {Да, кнопка выбрана: выдаем нужную команду} 

end;

В этом фрагменте не показанная здесь функция HotKey выделяет из надписи на кнопке символ командной клавиши (он обрамляется символом «~»), а стандартная для Turbo Vision функция GetAltCode преобразует этот символ в расширенный код клавиш Аlt-<клавиша>. Метод TButton.Press реализует «нажатие» на кнопку и выдает сообщение evBroadcast с командой TButton. Command.

Отметим, что рассмотренный пример приведен только в качестве иллюстрации: стандартный объект TButton реализует свой обработчик событий именно таким образом и Вам нет нужды переопределять его (по умолчанию экземпляр TButton инициируется с установленными флагами ofPreProcess и ofPostProcess).

18.5. КОМАНДЫ

Поскольку события обычно связаны с какими-то действиями пользователя программа должна, как правило, реагировать на эти действия изменением видимого изображения. С точки зрения Turbo Vision это означает, что обработчики событий должны преобразовывать события в действия, связанные с реакцией на произошедшее событие. Для реализации этих действий в Turbo Vision предусмотрены команды.

Команда - это просто целое число без знака, кодирующее определенную последовательность действий. В Turbo Vision предусмотрен ряд стандартных команд для реализации многих стандартных действий. Например, команда cmQuit реализует завершение работы программы и выход в ДОС, cmClose закрывает активное окно, cmZoom распахивает окно на весь экран или возвращает ему прежние размеры и т.д. Идентификаторы стХХХХ являются идентификаторами предопределенных констант, которые кодируют стандартные действия (например, cmQuit = 1, cmZoom = 5 и т.д.).

В своей программе Вы можете наряду со стандартными командами определить и использовать собственные команды для реализации специфических действий. Для этого необходимо создать свою константу-команду и в нужный момент сообщить видимым элементам о необходимости выполнить ее. Разумеется, Вы можете использовать произвольный идентификатор для вновь определяемой команды, однако Ваша программа станет намного понятнее, если при образовании новых идентификаторов Вы будете следовать каким-то правилам. В этом смысле использование префикса cm в идентификаторах новых команд кажется вполне логичным.

18.5.1. Преобразование активных событий в команды

Как указать на необходимость выполнения команды? Для этого в Turbo Vision Вы просто создаете событие-команду (evCommand), в поле Command которой помещаете код нужной команды. Например:

const

cmMyCommand =100;

.....

{Ниже показан фрагмент обработчика событий:}

Event.What := evCommand; {Определяем событие-команду} 

Event.Command := cmMyCommand; {Указываем код команды} 

Event.InfoPtr := NIL; {Признак активного события} 

PutEvent(Event); {Создаем событие}

В этом фрагменте событие-команда создается обращением к методу PutEvent. Заметим, что поле Event.InfoPtr должно содержать NIL, если событие активно; если событие уже обработано, для его очистки используется стандартный метод ClearEvent, который помещает в поле What признак evNothing, а в поле InfoPtr - указатель @Self (указатель на таблицу виртуальных методов объекта). Подробнее об использовании поля Event.InfoPtr см.п.18.7.

Вновь созданное событие вернется модальному элементу, который должен знать, как его обрабатывать. Например, часто событие-команда создается обработчиком событий строки статуса как реакция на нажатие предусмотренной в этой строке командной клавиши или отметку мышью соответствующего поля. В этом случае обработчик событий программы может иметь такой вид:

Procedure MyProgram.HandleEvent(var Event);

.....

begin

Inherited HandleEvent(Event); 

case Event.What of 

evCommand:

begin {Обработать команды:} 

case Event.Command of

cmMyCommand: MyProcedure; {Выполнить действия, связанные с командой cmMyCommand}

.....

else

exit {He обрабатывать непредусмотренные команды} 

end; {case}

ClearEvent(Event) {Очистить событие} 

end;

.....

end;

Часто модальным элементом является диалоговое окно с несколькими кнопками. Если Вам необходимо связать с кнопками свои команды и получить выбранную в диалоге команду, Вы можете закрыть окно с помощью вызова EndModal, передав этому методу в качестве параметра код команды.

В следующем примере создается диалоговое окно с двумя кнопками. При нажатии кнопки «Команда cmPrint» окно закроется и на экран будет выведена строка

Действие команды cmPrint

Если нажать кнопку «Выход» или закрыть окно клавишей Esc, эта строка не появится.

Uses CRT,App,Dialogs,Objects,Drivers,Views; 

type

PProg = ^TProg;

TProg = object (TApplication)

Constructor Init; 

end;

PDial = ^TDial;

TDial = object (TDialog)

Procedure HandleEvent(var Event: TEvent); Virtual;

end; 

const

cmPrint = 100; Constructor TProg.Init; 

var

R: TRect;

Dia: PDial; 

begin

Inherited Init;

R.Assign(20,9,60,17);

Dia := New(PDial, Init(R,''));

R.Assign(3,4,22,6) ;

DiaA.Insert(New(PButton,

Init(R,'Команда cm~P~rint',cmPrint,bfDefault)));

R.Assign(23,4,35,6);

DiaA.Insert(New(PButton,Init(R,'Выход',cmCancel,bfNormal)));

if ExecView(Dia) = cmPrint then

begin

{Вывод сообщения "в лоб", с помощью стандартных средств Турбо Паскаля. В.- TurboVision есть более удобные способы вывода сообщений}

GotoXY(30,12);

TextColor(Black);

TextBackground(White);

Write (' Действие команды cmPrint ')

end

end; {TProg.Init}

Procedure TDial.HandleEvent(var Event: TEvent); 

begin

Inherited HandleEvent(Event);

if (Event.What = evCommand) and

(Event.Command = cmPrint) then EndModal(cmPrint) 

end; {TDial.HandleEvent) 

var

Prog: TProg; 

begin

Prog.Init;

Prog.Run;

Prog.Done 

end.

В обработчике событий диалогового окна TDial.HandleEvent вначале вызывается стандартный обработчик TDialog.HandleEvent. Это дает возможность кнопке «Команда cmPrint» преобразовать событие, связанное с ее выбором, в команду cmPrint. Вновь созданное событие возвращается обработчику TDialHandleEvent, т.к. именно он является обработчиком событий модального элемента. Возвращаемая модальным элементом команда служит значением стандартной функции ExecView. Для упрощения программы вывод сообщения реализуется стандартными средствами Турбо Паскаля. В Turbo Vision имеется процедура MessageBox, обеспечивающая более удобный вывод сообщений.

18.5.2. Запрещение и разрешение команд

В качестве значения константы-команды можно использовать любое число в диапазоне от О до 65535, однако следует учесть, что диапазоны 0...99 и 2S6...999 Turbo Vision резервирует для стандартных команд и их не следует использовать для определения команд пользователя. Два диапазона зарезервированных команд выбраны потому, что команды с кодами 0...255 можно временно запретить, в то время как остальные команды запретить невозможно. Для запрещения или разрешения команд используется глобальный тип TCommandSet, представляющий собой множество чисел в диапазоне 0...256 (мощность множеств в Турбо Паскале не может превышать 256, вот почему запретить можно только первые 256 команд). Команды запрещаются обращением к методу DisableCommands, а разрешаются обращением к EnableCommands. Диалоговые элементы, связанные с запрещенными командами, выделяются оттенком и их нельзя выбрать мышью или командными клавишами. Например, если в конструкторе TProgJnit (см. предыдущий пример) перед оператором

if ExecView(Dia) = cmPrint then

вставить оператор

DisableCommand([cmPrint]);

кнопка «Команда cmPrint» будет выведена цветом фона окна и станет недоступна для диалога.

18.6. МОДИФИКАЦИЯ И ОБРАБОТКА СОБЫТИЙ

18.6.1. События, определенные пользователем

Старшие разряды поля Event. What используются для указания на то, что событие относится к классу сообщений. Первые шесть разрядов в этом поле программист может использовать для определения собственных классов событий.

Необходимость в новых классах событий может возникнуть в том случае, когда Ваша программа работает с нестандартными источниками информации. Если, например, Вы собираетесь работать с последовательным портом, Вы, возможно, захотите определить класс событий evSerial, используя для его идентификации один или несколько старших разрядов поля Event.What. Технически получить информацию из нового источника и сделать ее событием можно путем перекрытия метода TProgram.GetEvent (см.п. 18.6.4) или «фонового» метода TProgram.Idle (см.п. 18.6.5).

По умолчанию все новые классы событий маскируются маской evMessage, т.е. считаются сообщениями. Такие события модальный элемент рассылает всем своим подэ-лементам в Z-порядке (см. 17.3.2). Если Вы хотите, чтобы новый класс событий передавался как позиционированные или активные события, Вы можете изменить маски этих событий. В Turbo Vision определены маски PositionalEvents и FocusedEvents. Первая позволяет всем видимым элементам рассматривать событие как позиционированное, вторая - как активное. По умолчанию маска PositionalEvents выделяет все биты evMouse, a FocusedEvents содержит evKeyboard. Если Вам понадобится, чтобы новый класс событий обрабатывался так же как активные или позиционированные события, Вам необходимо добавить к нужной маске биты, соответствующие этому классу в поле What.

Например, создан новый класс событий evSerial с маской $8000, т.е. этот класс связан со старшим разрядом поля What. Если потребуется обработать этот класс так же как активные события, нужно задать новую маску:

const

evSerial = $8000;

.....

FocusedEvents := FocusedEvents or evSerial;

Обратите внимание: при добавлении к любой маске новых разрядов следует применять поразрядные операции над целыми числами (операции or, and, not). He следует использовать операцию арифметического сложения (+), так как в этом случае разрешен поразрядный перенос и вновь полученная маска может оказаться не той, какую Вы хотели. Если, например, к маске evMouse прибавить единицу, получится маска evKeyboard, т.е.

evKeyboard = evMouse + 1

В то же время операция

evMouse or 1

не вызовет поразрядного переноса и маска evMouse останется прежней, т.к. ее младший бит уже установлен.

18.6.2. Маскирование и очистка событий

Каждый видимый элемент имеет поле EventMask. По умолчанию значение этого поля устанавливается таким образом, чтобы видимый элемент обрабатывал все необходимые ему события и не откликался на другие. Например, TProgam и TDialog имеют EventMask = $FFFF, что позволяет им откликаться на любые возможные события, в том числе и определенные программистом. Кнопка TButton имеет EventMask = $0311, т.е. откликается на события evBroadcast, evCommand, evKeyDown, evMouseAuto, evMouseUp и evMouseDown. Как видим, ей доступны все стандартные события, кроме evMouseMove - кнопка не может реагировать на перемещение мыши.

Соответствующей установкой поля EventMask Вы можете запретить или разрешить любому видимому элементу реагировать на те или иные события. Например, Вы можете создать кнопку, доступ к которой возможен только с помощью мыши:

var

MouseButton: PButton;

.....

MouseButton := New(PButton, Init(...)); 

MouseButton^.EventMask := evMouse; 

nsert(MouseButton);

Для очистки события следует обратиться к методу ClearEvent, который устанавливает в поле Event. What значение evNothing, а в поле Event.InfoPtr - адрес таблицы виртуальных методов объекта. Таким образом, в поле Event.InfoPtr остается «подпись» видимого объекта, который очистил событие. Эта информация используется для организации межэлементного взаимодействия (см.п.18.7).

18.6.3. Перекрытие HandleEvent

После того как Вы определили команду и установили элемент управления, который генерирует ее (например, элемент меню или кнопка диалогового окна), Вам нужно научить Ваш видимый элемент реагировать на возникновение этой команды.

Каждый видимый элемент наследует обработчик событий - метод HandleEvent, который уже знает, как реагировать на большую часть ввода пользователя. Если Вы хотите, чтобы видимый элемент делал что-то специфическое для Вашей программы, Вам необходимо перекрыть HandleEvent и научить новый обработчик событий двум вещам - как откликаться на определенные Вами команды и как реагировать на события от мыши и клавиатуры нужным Вам образом.

Метод HandleEvent определяет поведение видимого элемента. Два видимых элемента с идентичными методами HandleEvent будут одинаково откликаться на события. Когда Вы порождаете новый тип видимого элемента, Вы обычно хотите, чтобы его поведение более или менее соответствовало поведению его предка с некоторыми изменениями. Наиболее простой способ достичь этого - вызвать HandleEvent предка в методе HandleEvent нового объекта.

Общий вид HandleEvent наследника:

procedure NewDescendant.HandleEvent(var Event: TEvent); 

begin

{Код, изменяющий или ограничивающей унаследованное поведение}

Inherited HandleEvent(Event); 

{Код, выполняющий дополнительные функции} 

end;

Другими словами, если Вы хотите, чтобы новый объект обрабатывал события не так, как это делал его предок, Вы должны перехватить определенные события до передачи события в метод HandleEvent предка. Если Вы хотите, чтобы Ваш новый объект вел себя также, как его предок, но с дополнительными функциями, Вы должны добавить код после вызова процедуры HandleEvent предка.

18.6.4. Перекрытие GetEvent

Единственным источником событий является метод TView.GetEvent. Только этот метод обращается к внешним источникам информации - клавиатуре и мыши. Если в Вашей программе используются другие устройства ввода информации (например, джойстик или коммуникационный канал), Вы должны перекрыть этот метод и научить его работе с нестандартными устройствами.

Проще всего перекрыть метод можно при объявлении нового типа Вашей программы, например:

Арр,...;

Uses type

MyProgram = object (TApplication)

Procedure GetEvent(var Event: TEvent);Virtual;

.....

end;

Procedure MyProgram.GetEvent(var Event: TEvent); 

begin

'TApplication.GetEvent(Event); 

if Event.What = evNothing then 

begin

{Обращение к нестандартным источникам информации} 

end 

end;

Поскольку MyProgram в конечном счете наследует GetEvent от TView, все видимые элементы Вашей программы будут пользоваться новым источником информации.

Преимущества централизованного сбора событий очевидны. Перекрывая единственный метод GetEvent, Вы можете заставить программу реагировать на внешнюю информацию любым удобным для Вас способом. Например, можно перехватывать заранее обусловленные командные клавиши и развертывать их в целую серию событий. Таким способом легко создавать различного рода макрокоманды.

18.6.5. Неиспользованное время

Поскольку программа, работающая в среде Turbo Vision, рассчитана на диалог с пользователем, в ней всегда найдутся такие промежутки времени, в течение которых она просто пассивно ожидает действий пользователя. Turbo Vision предоставляет Вам удобное средство, позволяющее в этот момент загрузить процессор не слишком долгой, но нужной для Вас работой.

Стандартный метод TView.GetEvent построен таким образом, что если нет никаких событий, он обращается к псевдоабстрактному методу TView.Idle. По умолчанию TView.Idle ничего не делает, он просто возвращает управление методу GetEvent, заставляя его непрерывно сканировать клавиатуру и мышь. Вы можете перекрыть TView.Idle, чтобы выполнить нужные действия.

В следующем примере каждые 5 сек в правый верхний угол экрана выводится системное время. Для упрощения программы вывод осуществляется стандартными средствами Турбо Паскаля.

Uses DOS,CRT,App; 

type

TProg = object (TApplication)

Procedure Idle; Virtual; 

end;

Procedure TProg.Idle; 

const

old: Byte = 0; {Старое значение секунд} 

dt = 5; {Шаг вывода} 

var

ho,mi,se,s100: Word; Function TimeStr(k: Word): String; 

var

s: String [2]; 

begin

str(k,s);

if k < 10 then

s := '0'+s; 

TimeStr := s 

end; {TimeStr} 

begin {TProg.Idle}

GetTime(ho,mi,se,s100); 

if (se mod dt = 0) and (old <> se) then 

begin

Old := Se; T

extColor(Black) ; 

TextBackGround(White); 

GotoXY(72,1);

Write(TimeStr(ho)+ ':'+TimeStr(mi) + ':'+TimeStr(se)) 

end

end; {TProg.Idle} 

var

Prog:TProg; 

begin

Prog.Init; 

Prog.Run; 

Prog.Done 

end.

Разумеется, не следует поручать методу TView.Idle слишком сложную работу, иначе пользователь Вашей программы будет безуспешно нажимать на клавиши, пытаясь вернуть к жизни «зависшую» программу. Предполагается, что рабочий цикл метода не будет превышать нескольких сотен миллисекунд. Если все-таки Вы хотите выполнить достаточно длинную фоновую задачу, попытайтесь разбить ее на серию мелких шагов.

18.6.6. Ненужные события

Некоторые события могут оказаться ненужными в данном контексте программы. Например, пользователь может нажать командную клавишу, временно запрещенную для использования, или отметить мышью поле вне текущего диалогового окна. Ненужные события - это события, обработка которых не предусмотрена в данном модальном элементе или в любом из его подэлементов. Такие события возвращаются модальному элементу, который в этом случае вызывает свой виртуальный метод EventError. Этот метод вызывает метод EventError своего владельца и так происходит до тех пор, пока не будет вызван метод TApplication.EventError. По умолчанию метод Т Application.EventError просто ничего не делает.

Вы можете перекрыть метод EventError любого видимого элемента (или программы), чтобы, например, сообщить пользователю о его ошибке и/или дать справку о возможностях программы в данный момент. Кроме того, контроль за ненужными событиями может быть полезен на этапе отладки программы.

18.7. ВЗАИМОДЕЙСТВИЕ ВИДИМЫХ ЭЛЕМЕНТОВ

Иерархия объектов Turbo Vision построена так, чтобы любой объект имел все необходимые для его работы поля и методы. Взаимодействие видимых элементов обычно осуществляется путем создания и использования групп. Если у Вас возникнет необходимость организовать взаимодействие не связанных друг с другом объектов, следует прежде всего тщательно проанализировать программу: возможно Вы не использовали всех средств Turbo Vision или некорректно разделили задачи между двумя видимыми объектами. В большинстве случаев задача может быть решена путем создания нового объекта, объединяющего в себе свойства двух других.

Если программа спроектирована правильно и видимые элементы требуют взаимодействия между собой, можно создать объект-посредник. Типичным примером такого объекта является внутренний буфер Clipboard диалоговой среды Турбо Паскаля (опция Edit). Для передачи фрагмента текста из одного окна редактора в другое фрагмент помещается в буфер командными клавишами Shift-Del или Ctrl-Ins, затем вызывается другое окно и фрагмент вставляется в нужное место командой Shift-Ins. Вы можете организовать такого рода буфер и в Вашей программе, так как с помощью Turbo Vision Вам доступны все средства диалоговой среды Турбо Паскаля. Преимуществом создания объекта-посредника является то, что с его помощью легко решается проблема взаимодействия сразу нескольких объектов друг с другом. Например, если Вы создаете сложную интегрированную систему, включающую текстовый редактор, систему управления базами данных, электронную таблицу и сервисные средства, промежуточный буфер позволит передать данные из текстового редактора в базу данных, или из базы данных в электронную таблицу и т.д.

Другим способом организации взаимодействия элементов является использование событий-сообщений. Эти события создаются с помощью глобальной функции Message. Функция Message описана в интерфейсной части модуля Views следующим образом:

Function Message (Receiver: Pview; What, Command: Word;

InfoPtr: Pointer): Pointer;

Первым параметром указывается ссылка на экземпляр объекта, которому адресуется сообщение. Остальные параметры используются для создания записи TEvent. Функция создает запись события и, если это возможно, вызывает метод Receiver^ .HandleEvent для обработки этого события. Если адресат не существует или при обращении к функции указан параметр Receiver = NIL, функция возвращает NIL - это означает, что событие не было обработано. Если событие успешно обработано (Receiver^. HandleEvent возвращает событие с полем What = evNothing), функция вернет указатель Event.InfoPtr.

Как уже говорилось в п. 18.6.2, стандартный метод ClearEvent очищает событие, устанавливая Event. What = evNothing и Event.InfoPtr = @Self. Таким образом, объект, обработавший и очистивший событие, оставляет в Event.InfoPtr указатель на свою таблицу виртуальных методов. Этот указатель позволяет полностью идентифицировать объект-получатель сообщения и организовать связь с ним.

Рассмотрим пример. В диалоговой среде Турбо Паскаля используется окно отладки, с которым Вы, очевидно, хорошо знакомы.. Если программист потребовал открыть это окно, среда должна проверить, открывалось ли оно ранее: если да, то окно просто переносится наверх (в смысле Z-упорядочения), если нет, создается вновь. Для реализации проверки среда дает сообщение

AreYouThere := Message(DeskTop,evBroadcast, cmFindWatchWindow, NIL);

В методе HandleEvent окна отладки есть проверка на команду cmFindWatchWindow:

if (Event.What = evBroadcast) and

(Event.Command = cmFindWatchWindow) then 

ClearEvent(Event);

Если окно существует, оно очистит событие и оставит в нем свою «подпись», поэтому сразу за передачей сообщения реализуется такая проверка:

if AreYouThere = NIL then

CreateWatchWindow {Создать новое окно} 

else

AreYouThereA.Select; {Поместить существующее окно наверх}

Поскольку окно отладки - это единственный объект, который знает, как реагировать на команду cmFindWatchWindow, в диалоговой среде Турбо Паскаля может использоваться только одно окно этого типа.

Таким же способом компилятор Турбо Паскаля определяет окно редактора, из которого следует брать текст компилируемой программы. Это окно - всегда верхнее на экране, поэтому компилятор посылает сообщение с командой, на которую откликаются только окна редактора. Так как событие-сообщение передается в Z-порядке, первое откликнувшееся окно, т.е. окно, очистившее событие, и будет самым верхним.

Видимый элемент может послать сообщение, установив в поле Event.InfoPtr указатель на собственную таблицу виртуальных методов. Это даст возможность получателю сообщения при необходимости обратиться к методам объекта-отправителя.

Организовать взаимодействие можно также с помощью прямого вызова обработчика событий нужного объекта. Для этого экземпляр объекта-адресата (переменная типа объект или указатель на него) должен быть инициирован предыдущим обращением к конструктору и программа должна «видеть» его, т.е. он должны быть глобальным по отношению к инициатору взаимодействия.

18.8. КОНТЕКСТНАЯ ПОМОЩЬ

В Turbo Vision предусмотрены средства, облегчающие создание контекстно-зависимой справочной службы. С этой целью каждый видимый элемент имеет специальное шестнадцатиразрядное поле TView.HelpCtx, содержимым которого Вы можете распоряжаться по своему усмотрению. Обычно каждому видимому элементу присваивается свой код (целое число в диапазоне от 0 до 65535), устанавливаемый в поле HelpCtx. В этом случае при нажатии на заранее обусловленную командную клавишу, открывающую доступ к справочной службе (обычно это клавиша F1), программа может получить текущий контекст (прямым чтением поля HelpCtx или с помощью метода GetHelpCtx) и передать его в качестве параметра вызова справочной службе.

Где обрабатывать событие, связанное с нажатием клавиши вызова справочной службы? Идеальным местом для этого является источник всех событий - метод GetEvent. Этот метод связан с любым видимым элементом, в том числе и с терминальным видимым объектом, и поэтому без труда может получить текущий контекст.

В следующем примере на экране создается диалоговое окно с двумя кнопками. Клавиша F1 используется для доступа к справочной службе. Если активна (выбрана) левая кнопка, нажатие на F1 даст сообщение «Левая кнопка», если правая - «Правая кнопка». Если на экране нет диалогового окна (оно вызывается клавишей F2), появится сообщение «Нет окна».

Uses CRT,App,Dialogs,Obj ects,Drivers,Views,Menus; 

type

PProg = ^TProg;

TProg = object (TApplication)

Procedure HandleEvent(var Event: Tevent); Virtual; 

Procedure GetEvent(var Event: Tevent); Virtual; 

Procedure InitStatusLine; Virtual;

end;

Procedure TProg.HandleEvent(var Event: TEvent); 

Procedure Dialoglnit; 

var

R: TRect;

Dia: PDialog; 

B1,B2: PButton;

с: Word; 

begin

ClearEvent(Event);

R.Assign(20,9,60,17);

Dia := New(PDialog, Init(R,''));

R.Assign(3,4,19,6);

Bl := New(PButton,Init(R,'Левая',0,0));

El*.HelpCtx := 1;

DiaA.insert (B1);

R.Assign(20,4,35,6);

B2 := New(PButton,Init(R,'Правая',0,0));

.В2^. HelpCtx := 2;

Dia^.Insert (B2) ; 

с := ExecView(Dia) 

end ; {Dialоg Init} 

begin {TProg.HandleEvent }

TApplication.HandleEvent (Event) ; 

if (Event. What = evCommand)

(Event. Command = cmMenu) then 

Dialoglnit

end; {TProg. Handl eEvent} 

Procedure TProg. InitStatusLine; 

var

R: TRect; 

begin

GetExtent (R) ; 

R.A.Y := pred(R.B.Y) ;

StatusLine := New(PStatusLine, Init(R, 

NewStatusDef (О, О, {Начальная строка статуса} 

NewStatusKey ( ' ~Alt-X~ Выход' , kbAltX, cmQuit, 

NewStatusKey ( '~F1~ Справка' , kbFl,cmHelp, 

NewStatusKey ( '~F2~ Окно диалога' , kbF2, cmMenu, NIL))), 

NewStatusDef (1, 2 , {Строка статуса с диалоговым окном} 

NewStatusKey ( ' ~Esc~ Выход' , kbEsc, cmCancel, 

NewStatusKey ('~F1~ Справка' , kbFl, cmHelp, NIL)), .NIL))));

end; {TProg. InitStatusLine}

Procedure TProg. GetEvent (var Event: TEvent) ; 

const

txt: array [0..2] of String = ('Нет окна', 'Левая кнопка' , 'Правая кнопка'); 

begin

TApplication. GetEvent (Event) ; 

if Event . Command = cmHelp then 

begin

GotoXY(60,l) ; 

TextColor (Black+Blink); 

TextBackGround (White) ; 

Write (Txt [GetHelpCtx] ) ; 

Delay (2000) ; 

GotoXY(60,l) ; 

Write ('                  ') 

end

end; {TProg. GetEvent} 

var

Prog: TProg; 

begin

Prog.Init; 

Prog . Run ; 

Prog . Done 

end.

Для упрощение программы справочное сообщение выводится стандартными средствами Турбо Паскаля. Сообщение появляется в верхнем правом углу экрана и через 2 сек стирается, в течение этого промежутка времени доступ к клавиатуре и мыши блокируется.

Контекст видимого элемента может управлять содержимым строки статуса. С этой целью в методе InitStatusLine используется два или более вызова NewStatusDef со своим диапазоном контекста (см. предыдущий пример).

В состав демонстрационных модулей Turbo Vision включен файл HelpFile.pas и компилятор TVHC.pas, существенно упрощающие процесс создания и использования контекстно-чувствительной помощи.

Средства модуля HelpFile позволяют по заданному контексту найти в особом файле справочной службы нужную справку и вывести ее на экран. Текст справки помещается в окно со скроллером, границы которого могут изменяться. Справочная служба контролирует размер окна и форматирует текст так, чтобы строки не выходили за границы окна. В тексте справки могут использоваться перекрестные ссылки, представляющие собой выделенные цветом фрагменты текста. Справочная служба обеспечивает доступ к этим ссылкам так, как это реализовано в среде Турбо Паскаля: ссылку можно выбрать клавишей Tab или отметкой мышью, после чего нажатие на Enter приведет к раскрытию нового справочного окна с соответствующей справкой.

Для использования этих средств необходимо предварительно создать файл справочной службы, содержащий текстовые данные и средства управления ими. Файл справочной службы создается программой TVHC.PAS из обычного текстового файла, в котором используются следующие соглашения:

Заголовок темы содержит идентифицирующее справку уникальное кодовое слово, и связанный с ним контекст. Например:

.topic Viewer=2

Здесь Viewer - кодовое слово; 2 - контекст справочной службы.

Все остальные строки до очередного заголовка темы составляют справочное сообщение и будут выводиться в окне справочной службы. Если очередная строка справки начинается пробелом, эта строка не форматируется и может отсекаться границами окна, в противном случае строка выводится с учетом текущих границ окна (если очередное слово не умещается в окне, остаток текстовой строки переносится на следующую строку окна). Например:

.topic FileOpen=3 

File Open

---------

Эта опция меню используется для загрузки файла

Здесь строки

File Open

---------

начинаются пробелом и поэтому не форматируются, т.е. сохраняют свой вид независимо от границ окна, в то время как при выводе строки

Эта опция меню используется для загрузки файла

будут контролироваться границы окна (строка не начинается пробелом) и, если очередное слово выйдет за его правую границу, остаток строки вместе с неуместившимся словом будет выведен в следующей строке окна.

Любой фрагмент строки справочного сообщения, обрамленный фигурными скобками, рассматривается как перекрестная ссылка. Перекрестная ссылка может содержать кодовое слово заголовка нужной справки или произвольный текст, за которым указывается двоеточие и кодовое слово. Например:

С помощью опции {FileOpen} можно загрузить файл.

или

Окно обеспечивает {просмотр файла: Viewer} в режиме скроллера.

В первом случае будет выведена строка

С помощью опции FileOpen можно загрузить файл.

причем слово FileOpen будет выделено цветом и может использоваться как перекрестная ссылка на справку, заголовок темы которой имеет вид

.topic FileOpen = ... 

Во втором случае в окне выводится строка

Окно обеспечивает просмотр файла в режиме скроллера.

Слова просмотр файла выделяются цветом и используются как перекрестная ссылка на справку с заголовком

.topic Viewer = ... 

В заголовке темы можно указывать несколько кодовых слов, например:

.topic FileOpen=3, OpenFile=103, FFileOpen 

Доступ к такой справке возможен для контекстов

const

chFileOpen = 3; 

chOpenFile = 103; 

chFFileOpen= 104;

Обратите внимание: за кодовым словом FFileOpen не указывается контекст, в этом случае считается, что связанный с ним контекст есть предыдущий контекст, увеличенный на 1, т.е.

chFFileOpen = 103 + 1 = 104

Файл DEMOHELP. TXT содержит пример исходного текстового файла, подготовленного с учетом описанных требований для преобразования программой TVHC.PAS в файл справочной службы.

  1. Подготовка справочного файла и его использование осуществляется в следующей последовательности.
  2. Подготавливается текстовый файл NAMETEXT, содержащий заголовки тем, справки и перекрестные ссылки.
  3. Вызывается программа TVHC.EXE (исходный текст этой программы содержится в файле \BP\EXAMPLES\DOS\TVDEMOS\TVHC.PAS) для создания файла справочной службы NAMEHELP и вспомогательного модуля NAMEPAS. Обращение к программе описано ниже.

Действия 3,6,..., 3,д осуществляются каждый раз, когда необходимо выдать ту или иную справку.

Для преобразования текстового файла во внутренний формат, используемый средствами модуля HelpFile, предназначена программа, исходный текст которой поставляется в файле TVHC.PAS. Перед использованием этой программы ее необходимо оттранслировать в ЕХЕ-файл. Формат вызова программы TVHC.EXE:

TVHC NAMETEXT [NAMEHELP [NAMEPAS]]

(в квадратных скобках указаны необязательные параметры). Здесь NAMETEXT - имя исходного текстового файла, NAMEHELP - имя выходного файла справочной службы, NAMEPAS - имя выходного файла, содержащего текст модуля с контекстами в виде констант chXXXX. Если имена выходных файлов опущены, будут созданы файлы с именем исходного файла и расширением HLP для файла справочной службы, PAS для текста модуля.

Текст файл NAMEPAS имеет следующий вид:

unit namepas;

interface

const

chTopicl = nl; 

chTopic2 = n2;

.....

chTopicN = nN; 

implementation 

end.

Поскольку этот файл создается программой TVHC.EXE автоматически, будьте внимательны при обращении к ней. Не рекомендуется опускать имена NAMEHELP и NAMEPAS: если имя исходного текстового файла совпадает с именем программы или

любого другого PAS-файла, старый файл будет стерт и на его месте будет создан файл с текстом модуля!

Для иллюстрации техники использования средств модуля HelpFile рассмотрим следующий пример. Пусть текст справочной службы помещен в файл HelpTest.txt и имеет следующий вид:

.topic NoContext=0

Добро пожаловать

в справочную службу системы Turbo Vision!

В текстовом файле для справочной службы Вы должны подготовить {заголовки тем: topic} и {перекрестные ссылки: CrossRef}. Весь текст от одного заголовка до другого представляет собой текст справки и выводится в окне справочной службы. При подготовке текста учитываются следующие соглашения:

если очередная строка начинается символом пробел, эта строка не будет форматироваться с учетом границ окна;

во всех остальных случаях выводимый в окне текст форматируется с учетом границ окна: слово, не умещающееся на строке, переносится на следующую строку экрана. Для преобразования текстового файла в файл справочной службы вызывается программа TVHC.EXE:

TVHC NAMETXT [NAMEHELP [NAMEPAS]]

Здесь NAMETXT - имя исходного текстового файла; NAMEHLP -имя выходного файла справочной службы; NAMEPAS - имя выходного файла, содержащего текст PAS-модуля с определением всех контекстов в виде констант chXXXX. Имя файла NAMEHELP можно не указывать - в этом случае выходной файл будет иметь имя входного файла и расширение HLP. Если не указан файл HELPPAS, будет создан файл с именем исходного и расширением PAS.

По умолчанию программа использует расширение ТХТ для входного файла, HLP для выходного файла справочной службы и PAS - для файла констант. .topic Topic=l Заголовок темы имеет следующий формат:

.topic Name[=N] [, Namel[=N2] [...]]

Здесь Name - имя темы (может состоять только-из латинских букв и цифр; разница в высоте букв игнорируется); N -контекст справочной службы, при котором выдается данная справка.

В квадратных скобках показаны необязательные параметры. Если опущен контекст N, программа присваивает соответствующей перекрестной ссылке контекст NPrev + 1, где NPrev - предыдущий определенный в программе контекст. .topic CrossRef=2

В произвольное место текста справки можно вставить так называемую перекрестную ссылку:

(text[:title])

Здесь () - фигурные скобки; text - произвольный текст или заголовок темы; title - заголовок темы; этот параметр вместе с двоеточием опускается, если text - заголовок темы.

Если Вы подготовите такой файл, то после вызова

tvhc helptest

будут созданы два файла: файл справочной службы helptest.hlp и файл модуля с определениями констант helptest.pas. Содержимое этого последнего файла будет таким:

unit helptest;

interface

const

hcCrossRef = 2;

hcNoContext= 0;

hctopic = 1; 

implementation 

end.

Следующая программа будет выдавать контекстно-зависимые справочные сообщения

из файла helptest.hlp при каждом нажатии на F1.

Uses App, Menus, Drivers, Views, Objects, HelpFile, Dialogs; 

const

cmChangeCtx =1000; 

type

PCtxView = ^TCtxView; 

TCtxView = object (TView)

Constructor Init; 

Procedure Draw; Virtual;

end; 

MyApp = object (TApplication) 

Ctx: PCtxView; 

Constructor Init;

Procedure InitStatusLine; Virtual; 

Procedure HandleEvent(var Event: TEvent); Virtual; 

end ;

PMyHelpWindow = AMyHelpWindow; 

MyHelpWindow = object (THelpWindow)

Function GetPalette: PPalette; Virtual; 

end;

Procedure MyApp.InitStatusLine; 

var

R: TRect;

begin

GetExtent(R); 

R.A.Y := pred(R.B.Y); 

StatusLine := New(PStatusLine,Init(R,

NewStatusDef(0, $FFFF,

NewStatusKey('~Alt-X~ Выход', kbAltX, cmQuit,

NewStatusKey('~F1~ Помощь',kbF1, cmHelp, 

NewStatusKey('~F2~ Изменить контекст',kbF2,

cmChangeCtx, 

NewStatusKey('~F5~ Распахнуть окно1, kbF5,

cmZoom, NIL)))}, 

NIL))) 

end;

Constructor MyApp.Init; 

begin

TApplication.Init; 

Ctx := NewtPCtxView, Init);

Insert(Ctx); 

RegisterHelpFile 

end;

Procedure MyApp.HandleEvent; 

var

HF: PHelpFile; 

HS: PDosStream; 

HW: PMyHelpWindow; 

const

HelpOpen: Boolean = False; 

Procedure DoHelp;

{Обеспечивает доступ к контекстно-зависимой справочной службе} 

var

С: Word;. 

begin

ClearEvent(Event);

{ Открываем DOS-поток: }

HS := New(PDosStream, Init('HELPTEST.HLP', stOpenRead));

{ Создаем и инициируем экземпляр объекта THelpFile: }

HF := New(PHelpFile, Init(HS));

HelpOpen := HSA.status = stOk;

if HelpOpen then

begin

{Создаем окно справочной службы и связываем его с потоком HS и текущим контекстом:}

HW := New(PMyHelpWindow, Init(HF, GetHelpCtx)); 

if ValidView(HW) <> NIL then 

begin

С := ExecView(HW); {Выдаем справку} 

Dispose(HW) {Ликвидируем окно} 

end;

HelpOpen := False 

end 

else

Dispose (HF, Done) 

end; 

begin

TApplication.HandleEvent (Event) ; 

case Event . Command of

cmHelp: if not HelpOpen then

DoHelp;  {Выдаем справку} 

cmChangeCtx: {Изменяем контекст по клавише F2} 

begin

if HelpCtx = 3 then

HelpCtx := 0 

else

inc (HelpCtx) ;

Ctx^.Draw;

ClearEvent (Event) 

end

end 

end;

Function MyHelpWindow.GetPalette; 

const

P = #16#17#18#19#20#47#21#13;

C: String [8] = P; 

begin

GetPalette := @C 

end ;

Constructor TCtxView. Init; 

var

R: TRect; 

begin

R. Assign (67 , 0, 80, 1) ;

TView.Init(R) ; 

end;

Procedure TCtxView. Draw; 

var

S: String;

B: TDrawBuffer;

C: Byte; 

begin

Str (Application^. HelpCtx, S) ;

S := 'Контекст = '+S;

С := GetColor(2) ;

MoveChar(B, ' ', C, Size.X) ;

MoveStr(B, S, C) ;

WriteLine(0, 0,Size.X,1,B) 

end; 

var

Р: МуАрр; 

begin

P.Init;

P.Run;

P.Done 

end.

В программе предусмотрена смена текущего контекста с помощью клавиши F2. Разумеется, в реальной программе смена контекста будет, судя по всему, происходить иначе: обычно в текст конструктора Init видимого элемента вставляется оператор

HelpCtx := NNN;

где NNN - нужный контекст.

Для визуализации текущего контекста в программе используется простой объект TCtxView, с помощью которого в верхнем правом углу экрана выводится строка

Контекст = N 

N - текущий контекст.